-
Notifications
You must be signed in to change notification settings - Fork 78
Add Harkan ABS game example (Abstract Testnet) #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
PR SummaryIntroduces a standalone example app under
Written by Cursor Bugbot for commit 1c50ce1. This will update automatically on new commits. Configure here. |
|
@shkudun is attempting to deploy a commit to the Abstract Foundation Team on Vercel. A member of the Team first needs to authorize it. |
| handleHit(); | ||
| } else { | ||
| handleMiss(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overlapping targets removed but only one hit counted
Low Severity
When a click lands on overlapping targets, the filter removes all of them from game.targets, but handleHit() is only called once afterward. This causes players to lose potential points — multiple targets disappear while only one hit is credited.
| const name = | ||
| profile.name && entry.player === walletAddress | ||
| ? profile.name | ||
| : `${entry.player.slice(0, 6)}...${entry.player.slice(-4)}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Address comparison fails due to case sensitivity
Medium Severity
The comparison entry.player === walletAddress uses strict equality between addresses with different casing. The walletAddress from eth_requestAccounts is typically lowercase, while entry.player from viem's log decoding is checksummed (mixed case). This case-sensitive comparison will always fail, so the user's profile name is never displayed next to their score in the on-chain leaderboard — only the truncated address appears.
|
|
||
| saveBtn.addEventListener("click", saveLocalScore); | ||
| submitBtn.addEventListener("click", submitOnChain); | ||
| refreshLeaderboard.addEventListener("click", renderLeaderboard); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refresh button shows local scores instead of on-chain
Medium Severity
The refreshLeaderboard button click handler is bound to renderLeaderboard(), which displays locally-stored scores. However, the button is in the LEADERBOARD panel alongside an on-chain transaction indicator, and the app is designed to show on-chain data when a contract is configured. The button likely needs to call refreshOnChainLeaderboard() instead, since clicking Refresh currently replaces any displayed on-chain leaderboard entries with local scores.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall. One non-blocking note: the example relies on CDN imports via esm.sh (React/Wagmi/Viem/AGW). That’s fine for a demo, but if the examples repo prefers local deps, we might want to add a package.json + local installs later. No functional issues found.
| if (age > target.ttl) { | ||
| handleMiss(); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Targets expire during pause causing unfair misses
Medium Severity
When the game is paused, performance.now() continues advancing while target born timestamps remain unchanged. Upon resuming, drawTargets() calculates target age as now - target.born, which includes the pause duration. Targets that were active before pausing may immediately exceed their TTL and trigger handleMiss(), penalizing the player for time they couldn't interact with the game.
Additional Locations (1)
| } catch (error) { | ||
| setStatus("Transaction failed"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing guard allows duplicate on-chain submissions
Medium Severity
The submitOnChain function has no guard to prevent concurrent submissions. While the transaction is pending (awaiting writeContract), the submit button remains enabled and canSubmitScore stays true. Users can click multiple times, triggering duplicate on-chain transactions for the same score, wasting gas and creating duplicate leaderboard entries. The React panel correctly uses isSubmitting to disable its button, but the vanilla JS version lacks equivalent protection.
| overlay.querySelector("p").textContent = | ||
| "Use your mouse or tap to lock onto signals. Each hit boosts your sync score."; | ||
| openModal(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enter Simulation button incorrectly opens help modal
Medium Severity
The enterBtn ("Enter Simulation") click handler sets up the game overlay correctly, but then incorrectly calls openModal() at line 803 which opens the "How it works" help dialog. This is the same modal that the separate howBtn ("How It Works") button opens at line 796. Users clicking "Enter Simulation" would unexpectedly see the help modal appear, obscuring the game area. The openModal() call appears to be an accidental inclusion.
| .request({ method: "eth_chainId" }) | ||
| .then(setChainInfo) | ||
| .catch(() => setChainInfo(null)); | ||
| window.ethereum.on("chainChanged", setChainInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing handler for wallet account changes
Medium Severity
The code handles the chainChanged event but is missing a handler for accountsChanged. When a user switches accounts in their wallet (e.g., MetaMask), walletAddress and walletClient remain stale. The UI continues displaying the old address, and submitOnChain() passes the old account to writeContract() at line 555. This causes a mismatch between the displayed account and the actual connected account, leading to failed or misdirected transactions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| updateProfile(entry); | ||
| renderLeaderboard(); | ||
| setStatus("Score saved locally", true); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scores can be saved multiple times per run
Medium Severity
After a run completes, canSubmitScore is set to true but neither saveLocalScore nor submitOnChain resets it to false after saving. The save/submit buttons remain enabled, allowing users to click "Save Local" repeatedly to add duplicate entries to the leaderboard and inflate profile stats (runs count, total hits/misses) with the same score data.
Summary
Demo
https://abs-game001.vercel.app/
Contract (Abstract Testnet)
0x51F2C923a5307E2701F228DcC4cB3D72B1aAb804
Test plan
PR-Codex overview
This PR introduces a cyberpunk mini-game titled
Harkan ABS, designed for theAbstract Testnet. It includes features like on-chain score submission, a leaderboard, and local running instructions, along with a user interface for interacting with a smart contract.Detailed summary
README.mdwith game description, demo link, contract address, and features.agw-panel.jsfor wallet integration and score submission.index.htmlfor the game interface, including a leaderboard and player profile.styles.css.app.js, including score tracking and gameplay mechanics.